#!/usr/bin/env bash
#
# mkmetakind
# Morgan Deters <mdeters@cs.nyu.edu> for cvc5
# Copyright (c) 2010-2013  The cvc5 Project
#
# The purpose of this script is to create metakind.h from a template
# and a list of theory kinds.
#
# This is kept distinct from kind.h because kind.h is a public header
# and metakind.h is intended for the expr package only.
#
# Invocation:
#
#   mkmetakind template-file theory-kind-files...
#
# Output is to standard out.
#
# Required to disable this option for bash >=5.2 to avoid automatically
# replacing & by the substituted text.
shopt | grep -q '^patsub_replacement\b' &&  shopt -u patsub_replacement

copyright=2010-2022

cat <<EOF
/******************************************************************************
 * This file is part of the cvc5 project.
 *
 * Copyright (c) $copyright by the authors listed in the file AUTHORS
 * in the top-level source directory and their institutional affiliations.
 * All rights reserved.  See the file COPYING in the top-level source
 * directory for licensing information.
 * ****************************************************************************
 *
 * This header file was automatically generated by:
 *
 *     $0 $@
 *
 * for the cvc5 project.
 */

EOF

me=$(basename "$0")

template=$1; shift

metakind_fwd_decls=
metakind_includes=
metakind_kinds=
metakind_constantMaps=
metakind_compares=
metakind_constHashes=
metakind_constPrinters=
metakind_constDeleters=
metakind_ubchildren=
metakind_lbchildren=
metakind_operatorKinds=
metakind_mkConst=
metakind_mkConstDelete=

seen_theory=false
seen_theory_builtin=false

payloads_seen=()

function contains {
  elem=$1
  shift
  arr=("$@")
  for e in "${arr[@]}"; do
    [[ "$e" == "$elem" ]] && return 0
  done
  return 1
}

function theory {
  # theory ID T header

  lineno=${BASH_LINENO[0]}

  if $seen_theory; then
    echo "$kf:$lineno: error: multiple theories defined in one file !?" >&2
    exit 1
  fi

  # this script doesn't care about the theory class information, but
  # makes does make sure it's there
  seen_theory=true
  if [ "$1" = THEORY_BUILTIN ]; then
    if $seen_theory_builtin; then
      echo "$kf:$lineno: error: \"builtin\" theory redefined" >&2
      exit 1
    fi
    seen_theory_builtin=true
  elif [ -z "$1" -o -z "$2" -o -z "$3" ]; then
    echo "$kf:$lineno: error: \"theory\" directive missing class or header argument" >&2
    exit 1
  elif ! expr "$2" : '\(::*\)' >/dev/null; then
    echo "$kf:$lineno: warning: theory class \`$1' isn't fully-qualified (e.g., ::cvc5::internal::theory::foo)" >&2
  elif ! expr "$2" : '\(::cvc5::internal::theory::*\)' >/dev/null; then
    echo "$kf:$lineno: warning: theory class not under ::cvc5::internal::theory namespace" >&2
  fi

  theory_class=$1
  metakind_includes="${metakind_includes}
// #include \"theory/$b/$2\""
}

function alternate {
  # alternate ID name T header

  lineno=${BASH_LINENO[0]}

  if $seen_theory; then
    echo "$kf:$lineno: error: multiple theories defined in one file !?" >&2
    exit 1
  fi

  seen_theory=true
  seen_endtheory=true

  theory_id="$1"
  name="$2"
  theory_class="$3"
  theory_header="$4"
  theory_includes="${theory_includes}#include \"$theory_header\"
"
}

function properties {
  # properties prop*
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function endtheory {
  # endtheory
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  seen_endtheory=true
}

function enumerator {
  # enumerator KIND enumerator-class header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function typechecker {
  # typechecker header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function typerule {
  # typerule OPERATOR typechecking-class
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function construle {
  # construle OPERATOR isconst-checking-class
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function rewriter {
  # rewriter class header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function sort {
  # sort TYPE cardinality [well-founded ground-term header | not-well-founded] ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function cardinality {
  # cardinality TYPE cardinality-computer [header]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function well-founded {
  # well-founded TYPE wellfoundedness-computer [header]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function variable {
  # variable K ["comment"]

  lineno=${BASH_LINENO[0]}

  check_theory_seen
  register_metakind VARIABLE "$1" 0
}

function operator {
  # operator K #children ["comment"]

  lineno=${BASH_LINENO[0]}

  check_theory_seen
  register_metakind OPERATOR "$1" "$2"
}

function parameterized {
  # parameterized K1 K2 #children ["comment"]

  lineno=${BASH_LINENO[0]}

  check_theory_seen
  register_metakind PARAMETERIZED "$1" "$3"
  if ! expr "$2" : '\[.*\]' &>/dev/null; then
    registerOperatorToKind "$1" "$2"
  fi
}

function constant {
  # constant K F T Hasher header ["comment"]

  lineno=${BASH_LINENO[0]}

  check_theory_seen

  if ! expr "$4" : '\(::*\)' >/dev/null; then
    echo "$kf:$lineno: warning: constant $1 hasher \`$4' isn't fully-qualified (e.g., ::cvc5::internal::RationalHashFunction)" >&2
  fi

  if [[ "$3" =~ '+'$ ]]; then
    # Remove last character
    class="${3%?}"
    skip_const_map=true
  else
    class="$3"
    skip_const_map=false
  fi

  if contains "${class}" "${payloads_seen[@]}"; then
    payload_seen=true
  else
    payload_seen=false
    payloads_seen=(${payloads_seen[@]} "${class}")
  fi

  if [[ "$2" != "skip" ]]; then
    metakind_fwd_decls="${metakind_fwd_decls}
$2 ${class};"
  fi

  # Avoid including the same header multiple times
  if [ -n "$5" ] && [[ "${metakind_includes}" != *"#include \"$5\""* ]]; then
    metakind_includes="${metakind_includes}
#include \"$5\""
  fi
  register_metakind CONSTANT "$1" 0

  if [[ "${payload_seen}" != true ]]; then
    metakind_constantMaps="${metakind_constantMaps}
// The reinterpret_cast of d_children to \"${class} const*\"
// flags a \"strict aliasing\" warning; it's okay, because we never access
// the embedded constant as a NodeValue* child, and never access an embedded
// NodeValue* child as a constant.
#pragma GCC diagnostic ignored \"-Wstrict-aliasing\"

template <>
${class} const& NodeValue::getConst< ${class} >() const {
  //AssertArgument(getKind() == ::cvc5::internal::Kind::$1, *this,
  //               \"Improper kind for getConst<${class}>()\");
  // To support non-inlined CONSTANT-kinded NodeValues (those that are
  // \"constructed\" when initially checking them against the NodeManager
  // pool), we must check d_nchildren here.
  return d_nchildren == 0
    ? *reinterpret_cast< ${class} const* >(d_children)
    : *reinterpret_cast< ${class} const* >(d_children[0]);
}

// re-enable the warning
#pragma GCC diagnostic warning \"-Wstrict-aliasing\"

"
  fi

  if [ "${skip_const_map}" != true ]; then
    metakind_mkConst="${metakind_mkConst}
template<>
Node NodeManager::mkConst<${class}>(const ${class}& val)
{
  return mkConstInternal<Node, ${class}>(::cvc5::internal::Kind::$1, val);
}
"
    metakind_mkConst="${metakind_mkConst}
template<>
Node NodeManager::mkConst(Kind k, const ${class}& val)
{
  return mkConstInternal<Node, ${class}>(k, val);
}
"
  elif [[ "${payload_seen}" != true ]]; then
    metakind_mkConstDelete="${metakind_mkConstDelete}
template<>
Node NodeManager::mkConst<${class}>(const ${class}& val) = delete;
"
    metakind_mkConst="${metakind_mkConst}
template<>
Node NodeManager::mkConst(Kind k, const ${class}& val)
{
  return mkConstInternal<Node, ${class}>(k, val);
}
"
  fi

  metakind_compares="${metakind_compares}
    case Kind::$1:
      return NodeValueConstCompare<Kind::$1, ${class}, pool>::compare(nv1, nv2);
"
  metakind_constHashes="${metakind_constHashes}
  case Kind::$1:
    return $4()(nv->getConst< ${class} >());
"
  metakind_constPrinters="${metakind_constPrinters}
  case Kind::$1:
    out << nv->getConst< ${class} >();
    break;
"
  cname=`echo "${class}" | awk 'BEGIN {FS="::"} {print$NF}'`
  metakind_constDeleters="${metakind_constDeleters}
  case Kind::$1:
    std::destroy_at(reinterpret_cast< ${class}* >(nv->d_children));
    break;
"
}

function nullaryoperator {
  # nullaryoperator K ["comment"]

  lineno=${BASH_LINENO[0]}

  check_theory_seen
  register_metakind NULLARY_OPERATOR "$1" 0
}

function registerOperatorToKind {
  operatorKind=$1
  applyKind=$2
  metakind_operatorKinds="${metakind_operatorKinds}
    case Kind::$applyKind: return Kind::$operatorKind;";
}

function register_metakind {
  mk=$1
  k=$2
  nc=$3

  metakind_kinds="${metakind_kinds}    metakind::$mk, /* $k */
";

  # figure out the range given by $nc
  if expr "$nc" : '[0-9][0-9]*$' >/dev/null; then
    lb=$nc
    ub=$nc
  elif expr "$nc" : '[0-9][0-9]*:$' >/dev/null; then
    let `echo "$nc" | awk 'BEGIN{FS=":"}{print"lb="$1}'`
    ub="expr::NodeValue::MAX_CHILDREN"
  elif expr "$nc" : '[0-9][0-9]*:[0-9][0-9]*$' >/dev/null; then
    let `echo "$nc" | awk 'BEGIN{FS=":"}{print"lb="$1" ub="$2}'`
    if [ $ub -lt $lb ]; then
      echo "$kf:$lineno: error in range \`$nc': LB < UB (in definition of $k)" >&2
      exit 1
    fi
  else
    echo "$kf:$lineno: can't parse range \`$nc' in definition of $k" >&2
    exit 1
  fi

  metakind_lbchildren="${metakind_lbchildren}
    $lb, /* $k */"
  metakind_ubchildren="${metakind_ubchildren}
    $ub, /* $k */"
}

# Returns 0 if arg is a primitive C++ type, or a pointer to same; 1
# otherwise.  Really all this does is check whether we should issue a
# "not fully qualified" warning or not.
function primitive_type {
  strip=`expr "$1" : ' *\(.*\)\* *'`
  if [ -n "$strip" ]; then
    primitive_type "$strip" >&2
    return $?
  fi

  case "$1" in
    bool|int|size_t|long|void|char|float|double) return 0;;
    *) return 1;;
  esac
}

function check_theory_seen {
  if $seen_endtheory; then
    echo "$kf:$lineno: error: command after \"endtheory\" declaration (endtheory has to be last)" >&2
    exit 1
  fi
  if ! $seen_theory; then
    echo "$kf:$lineno: error: no \"theory\" declaration found (it has to be first)" >&2
    exit 1
  fi
}

function check_builtin_theory_seen {
  if ! $seen_theory_builtin; then
    echo "$me: warning: no declaration for the builtin theory found" >&2
  fi
}

while [ $# -gt 0 ]; do
  kf=$1
  seen_theory=false
  seen_endtheory=false
  b=$(basename $(dirname "$kf"))
  metakind_kinds="${metakind_kinds}
    /* from $b */
"
  metakind_operatorKinds="${metakind_operatorKinds}

    /* from $b */"
  source "$kf"
  if ! $seen_theory; then
    echo "$kf: error: no theory content found in file!" >&2
    exit 1
  fi
  if ! $seen_endtheory; then
    echo "$kf:$lineno: error: no \"endtheory\" declaration found (it is required at the end)" >&2
    exit 1
  fi
  shift
done
check_builtin_theory_seen

## output

text=$(cat "$template")
for var in \
    metakind_fwd_decls \
    metakind_includes \
    metakind_kinds \
    metakind_constantMaps \
    metakind_compares \
    metakind_constHashes \
    metakind_constPrinters \
    metakind_constDeleters \
    metakind_ubchildren \
    metakind_lbchildren \
    metakind_operatorKinds \
    metakind_mkConst \
    metakind_mkConstDelete \
    template \
    ; do
  eval text="\${text//\\\$\\{$var\\}/\${$var}}"
done
error=`expr "$text" : '.*\${\([^}]*\)}.*'`
if [ -n "$error" ]; then
  echo "$template:0: error: undefined replacement \${$error}" >&2
  exit 1
fi
echo "$text"
